/*****************************************************\
* Copyright (c) 2020 The game2d Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/game2d.git
* Email:          505544956@qq.com
* FileName:       core/game.cc
* Create Time:    2020/03/21 12:14:44
* Description:    /
\****************************************************/
#include "game.h"

namespace g2d {
  Texture::Texture() : render_(0) {}
  Texture::~Texture() { uninit(); }
  Result Texture::init(SDL_Renderer *render, int flags) {
    if (!render) return Result::E_Fail;
    if (IMG_Init(flags) == 0) return Result::E_Fail;
    render_ = render;
    return Result::S_OK;
  }
  void Texture::uninit() {
    clear();
    IMG_Quit();
  }
  Result Texture::load(const char *id, SDL_Texture *texture) {
    if (!id || !*id || !texture) return Result::E_InvalidParam;
    if (get(id)) return Result::E_ExistedID;
    map_[id] = texture;
    return Result::S_OK;
  }
  Result Texture::load_file(const char *id, const char *file) {
    if (!id || !*id || !file || !*file) return Result::E_InvalidParam;
    if (get(id)) return Result::E_ExistedID;
    SDL_Texture *texture = IMG_LoadTexture(render_, file);
    if (!texture) return Result::E_Fail;
    map_[id] = texture;
    return Result::S_OK;
  }
  Result Texture::load_buffer(const char *id, const void* buffer, Uint32 len, const char *type) {
    if (!id || !*id || !buffer || len == 0) return Result::E_InvalidParam;
    if (get(id)) return Result::E_ExistedID;
    SDL_RWops *area = SDL_RWFromConstMem(buffer, len);
    if (!area) return Result::E_Fail;
    SDL_Texture *texture = type
        ? IMG_LoadTextureTyped_RW(render_, area, 0, type)
        : IMG_LoadTexture_RW(render_, area, 0);
    SDL_FreeRW(area);
    if (!texture) return Result::E_Fail;
    map_[id] = texture;
    return Result::S_OK;
  }
  void Texture::unload(const char *id) {
    if (!id || !*id) return;
    auto it = map_.find(id);
    if (it == map_.end()) return;
    SDL_DestroyTexture(it->second);
    map_.erase(it);
  }
  void Texture::clear() {
    for (auto &it : map_)
      SDL_DestroyTexture(it.second);
    map_.clear();
  }
  SDL_Texture* Texture::get(const char *id) const {
    if (!id || !*id) return 0;
    auto it = map_.find(id);
    if (it == map_.end()) return 0;
    return it->second;
  }

  Result Texture::size(const char *id, int *w, int *h) const {
    auto t = get(id);
    return size(t, w, h);
  }
  Result Texture::size(SDL_Texture *t, int *w, int *h) const {
    if (!t || !w || !h) return Result::E_InvalidParam;
    Uint32 f; int a;
    return SDL_QueryTexture(t, &f, &a, w, h) == 0 ? Result::S_OK : Result::E_Fail;
  }

  ///////////////////
  Font::Font() {}
  Font::~Font() { uninit(); }
  Result Font::init() {
    if (!TTF_WasInit() && TTF_Init() == -1)
      return Result::E_Fail;
    return Result::S_OK;
  }
  void Font::uninit() {
    clear();
    TTF_Quit();
  }
  Result Font::load(const char *id, const char *file, Uint32 size) {
    if (!id || !*id || !file || !*file) return Result::E_InvalidParam;
    if (get(id)) return Result::E_ExistedID;
    auto font = TTF_OpenFont(file, size);
    if (!font) return Result::E_Fail;
    map_[id] = font;
    return Result::S_OK;
  }
  Result Font::load(const char *id, const char *file, Uint32 size, int s) {
    if (!id || !*id || !file || !*file) return Result::E_InvalidParam;
    if (get(id)) return Result::E_ExistedID;
    auto font = TTF_OpenFont(file, size);
    if (!font) return Result::E_Fail;
    map_[id] = font;
    set_style(font, s);
    return Result::S_OK;
  }
  Result Font::load(const char *id, const char *file, Uint32 size
      , bool bold, bool italic, bool underline, bool strikethrough) {
    if (!id || !*id || !file || !*file) return Result::E_InvalidParam;
    if (get(id)) return Result::E_ExistedID;
    auto font = TTF_OpenFont(file, size);
    if (!font) return Result::E_Fail;
    map_[id] = font;
    set_style(font, bold, italic, underline, strikethrough);
    return Result::S_OK;
  }
  void Font::unload(const char *id) {
    if (!id || !*id) return;
    auto it = map_.find(id);
    if (it == map_.end()) return;
    TTF_CloseFont(it->second);
    map_.erase(it);
  }
  void Font::clear() {
    for (auto &it : map_)
      TTF_CloseFont(it.second);
    map_.clear();
  }
  TTF_Font* Font::get(const char *id) const {
    if (!id || !*id) return 0;
    auto it = map_.find(id);
    if (it == map_.end()) return 0;
    return it->second;
  }
  int Font::style(const TTF_Font *font) const {
    if (!font) return -1;
    return TTF_GetFontStyle(font);
  }
  void Font::set_style(TTF_Font *font, int s) {
    if (!font) return;
    TTF_SetFontStyle(font, s);
  }
  void Font::set_style(TTF_Font *font, bool bold, bool italic, bool underline, bool strikethrough) {
    if (!font) return;
    int style = TTF_STYLE_NORMAL;
    if (bold) style |= TTF_STYLE_BOLD;
    if (italic) style |= TTF_STYLE_ITALIC;
    if (underline) style |= TTF_STYLE_UNDERLINE;
    if (strikethrough) style |= TTF_STYLE_STRIKETHROUGH;
    TTF_SetFontStyle(font, style);
  }
  int Font::height(const TTF_Font *font) const {
    if (!font) return -1;
    return TTF_FontHeight(font);
  }
  int Font::style(const char *id) const {
    return style(get(id));
  }
  void Font::set_style(const char *id, int s) {
    auto font = get(id);
    set_style(font, s);
  }
  void Font::set_style(const char *id, bool bold, bool italic, bool underline, bool strikethrough) {
    auto font = get(id);
    set_style(font, bold, italic, underline, strikethrough);
  }
  int Font::height(const char *id) const {
    auto font = get(id);
    return  height(font);
  }

  Result Font::size(const char *fontid, const char *text, int *w, int *h) {
    auto f = get(fontid);
    return size(f, text, w, h);
  }
  Result Font::size(TTF_Font *f, const char *text, int *w, int *h) {
    if (!f) return Result::E_FontNotFound;
    if (TTF_SizeUTF8(f, text, w, h) != 0)
      return Result::E_Fail;
    return Result::S_OK;
  }

  ///////////////////
  Audio::Audio() : mute_volume_sfx_(0), mute_volume_music_(0) {}
  Audio::~Audio() { uninit(); }
  Result Audio::init(int flags, int frequency, int chunksize) {
    if (Mix_Init(flags) == 0)
      return Result::E_Fail;
    if (Mix_OpenAudio(frequency, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, chunksize) != 0)
      return Result::E_Fail;
    return Result::S_OK;
  }
  void Audio::uninit() {
    clear();
    Mix_CloseAudio();
    Mix_Quit();
  }
  Result Audio::load_sfx_file(const char *id, const char *file) {
    if (!id || !*id || !file || !*file) return Result::E_InvalidParam;
    auto sfx = Mix_LoadWAV(file);
    if (!sfx) return Result::E_Fail;
    sfxs_[id] = sfx;
    return Result::S_OK;
  }
  Result Audio::load_sfx_buffer(const char *id, const void* buffer, Uint32 len) {
    if (!id || !*id || len == 0) return Result::E_InvalidParam;
    auto sfx = Mix_LoadWAV_RW(SDL_RWFromConstMem(buffer, len), 1);
    if (!sfx) return Result::E_Fail;
    sfxs_[id] = sfx;
    return Result::S_OK;
  }
  Result Audio::load_music_file(const char *id, const char *file) {
    if (!id || !*id || !file || !*file) return Result::E_InvalidParam;
    auto music = Mix_LoadMUS(file);
    if (!music) return Result::E_Fail;
    musics_[id] = music;
    return Result::S_OK;
  }
  Result Audio::load_music_buffer(const char *id, const void* buffer, Uint32 len) {
    if (!id || !*id || len == 0) return Result::E_InvalidParam;
    auto music = Mix_LoadMUS_RW(SDL_RWFromConstMem(buffer, len), 1);
    if (!music) return Result::E_Fail;
    musics_[id] = music;
    return Result::S_OK;
  }
  void Audio::unload_sfx(const char *id) {
    if (!id || !*id) return;
    auto it = sfxs_.find(id);
    if (it == sfxs_.end()) return;
    Mix_FreeChunk(it->second);
    sfxs_.erase(it);
  }
  void Audio::unload_music(const char *id) {
    if (!id || !*id) return;
    auto it = musics_.find(id);
    if (it == musics_.end()) return;
    Mix_FreeMusic(it->second);
    musics_.erase(it);
  }
  void Audio::clear_sfx() {
    for (auto &it : sfxs_)
      Mix_FreeChunk(it.second);
    sfxs_.clear();
  }
  void Audio::clear_music() {
    for (auto &it : musics_)
      Mix_FreeMusic(it.second);
    musics_.clear();
  }
  void Audio::clear() {
    clear_sfx();
    clear_music();
  }
  Mix_Chunk* Audio::get_sfx(const char *id) const {
    if (!id || !*id) return 0;
    auto it = sfxs_.find(id);
    if (it == sfxs_.end()) return 0;
    return it->second;
  }
  Mix_Music* Audio::get_music(const char *id) const {
    if (!id || !*id) return 0;
    auto it = musics_.find(id);
    if (it == musics_.end()) return 0;
    return it->second;
  }

  Result Audio::play_sfx(const char *id, int loops) const {
    auto sfx = get_sfx(id);
    return play_sfx(sfx, loops);
  }
  Result Audio::play_music(const char *id, int loops) const {
    auto music = get_music(id);
    return play_music(music, loops);
  }
  Result Audio::play_sfx(Mix_Chunk *sfx, int loops) const {
    if (!sfx) return Result::E_SfxNotFound;
    Mix_PlayChannel(-1, sfx, loops);
    return Result::S_OK;
  }
  Result Audio::play_music(Mix_Music *music, int loops) const {
    if (!music) return Result::E_MusicNotFind;
    Mix_PlayMusic(music, loops);
    return Result::S_OK;
  }
  void Audio::pause_sfx() const { Mix_Pause(-1); }
  void Audio::pause_music() const { Mix_PauseMusic(); }
  void Audio::resume_sfx() const { Mix_Resume(-1); }
  void Audio::resume_music() const { Mix_ResumeMusic(); }
  void Audio::stop_sfx() const { Mix_HaltChannel(-1); }
  void Audio::stop_music() const { Mix_HaltMusic(); }
  bool Audio::playing_sfx() const { return Mix_Playing(-1) > 0; }
  bool Audio::playing_music() const { return Mix_PlayingMusic() != 0; }
  bool Audio::paused_sfx() const { return Mix_Paused(-1) > 0; }
  bool Audio::paused_music() const { return Mix_PausedMusic() != 0; }
  int Audio::volume_sfx() const { return Mix_Volume(-1, -1) * 100 / MIX_MAX_VOLUME; }
  int Audio::volume_music() const { return Mix_VolumeMusic(-1) * 100 / MIX_MAX_VOLUME; }
  void Audio::set_volume_sfx(int v) const { Mix_Volume(-1, v * MIX_MAX_VOLUME / 100); }
  void Audio::set_volume_music(int v) const { Mix_VolumeMusic(v * MIX_MAX_VOLUME / 100); }
  bool Audio::muted_sfx() const { return Mix_Volume(-1, -1) == 0; }
  bool Audio::muted_music() const { return Mix_VolumeMusic(-1) == 0; }
  bool Audio::muted() const { return Mix_Volume(-1, -1) == 0 && Mix_VolumeMusic(-1) == 0; }
  void Audio::mute_sfx(bool _mute) {
    auto v = Mix_Volume(-1, -1);
    if (_mute) {
      if (v == 0) return;
      mute_volume_sfx_ = v;
      Mix_Volume(-1, 0);
    } else {
      if (v != 0) return;
      Mix_Volume(-1, mute_volume_sfx_);
      mute_volume_sfx_ = 0;
    }
  }
  void Audio::mute_music(bool _mute) {
    auto v = Mix_VolumeMusic(-1);
    if (_mute) {
      if (v == 0) return;
      mute_volume_music_ = v;
      Mix_VolumeMusic(0);
    } else {
      if (v != 0) return;
      Mix_VolumeMusic(mute_volume_music_);
      mute_volume_music_ = 0;
    }
  }
  void Audio::mute(bool _mute) {
    mute_sfx(_mute);
    mute_music(_mute);
  }

  ///////////////////
  Input::Input() : keymod_(KMOD_NONE)
      , mouses_btn_(0), mouses_btn_last_(0) {
    mouse_pt_.x = 0; mouse_pt_.y = 0;
  }
  Input::~Input() { uninit(); }
  Result Input::init() {
    if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) {
      return Result::E_Fail;
    }

    for (int i = 0; i < SDL_NUM_SCANCODES; ++i) {
      keys_[i] = 0;
      keys_last_[i] = 0;
    }

    for (auto &it : joysticks_)
      SDL_JoystickClose(it);
    joysticks_.clear();
    joy_axis_.clear();
    joy_hats_.clear();
    joy_btns_.clear();
    joy_btns_last_.clear();

    for (int i = 0; i < SDL_NumJoysticks(); ++i) {
      auto joy = SDL_JoystickOpen(i);
      if (!joy) continue;
      if (!SDL_JoystickGetAttached(joy)) {
        SDL_JoystickClose(joy);
        continue;
      }

      std::vector<int> axis;
      for (int j = 0; j < SDL_JoystickNumAxes(joy); ++j) {
        axis.push_back(0);
      }
      joy_axis_.push_back(std::move(axis));

      std::vector<Uint8> hats;
      for (int j = 0; j < SDL_JoystickNumHats(joy); ++j) {
        hats.push_back(SDL_HAT_CENTERED);
      }
      joy_hats_.push_back(std::move(hats));

      std::vector<bool> btns;
      for (int j = 0; j < SDL_JoystickNumButtons(joy); ++j) {
        hats.push_back(false);
      }
      joy_btns_.push_back(btns);
      joy_btns_last_.push_back(std::move(btns));

      joysticks_.push_back(std::move(joy));
    }
    if (joysticks_.size() > 0)
      SDL_JoystickEventState(SDL_ENABLE);

    return Result::S_OK;
  }
  void Input::uninit() {
    for (auto &it : joysticks_)
      SDL_JoystickClose(it);
    joysticks_.clear();
    joy_axis_.clear();
    joy_hats_.clear();
    joy_btns_.clear();
    joy_btns_last_.clear();
    
    if (SDL_WasInit(SDL_INIT_JOYSTICK) != 0)
      SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
  }
  void Input::update() {
    // key
    {
      int num = 0;
      auto keys = SDL_GetKeyboardState(&num);
      if (keys) {
        for (int i = 0; i < num; ++i) {
          keys_last_[i] = keys_[i];
          keys_[i] = keys[i];
        }
      }
      
      keymod_ = SDL_GetModState();
    }
    
    // mouse
    mouses_btn_last_ = mouses_btn_;
    mouses_btn_ = SDL_GetMouseState(&mouse_pt_.x, &mouse_pt_.y);
    
    // joy
    joy_btns_.swap(joy_btns_last_);
    for (int i = 0; i < joysticks_.size(); ++i) {
      auto joy = joysticks_.at(i);
      if (!joy || !SDL_JoystickGetAttached(joy))
        continue;

      std::vector<int> &axis = joy_axis_.at(i);
      for (int j = 0; j < axis.size(); ++j) {
        axis.at(j) = SDL_JoystickGetAxis(joy, j);
      }

      std::vector<Uint8> &hats = joy_hats_.at(i);
      for (int j = 0; j < hats.size(); ++j) {
        hats.at(j) = SDL_JoystickGetHat(joy, j);
      }

      std::vector<bool> &btns = joy_btns_.at(i);
      for (int j = 0; j < btns.size(); ++j) {
        btns.at(j) = SDL_JoystickGetButton(joy, j);
      }
    }
  }
  
  bool Input::key_pressed(SDL_Scancode key, bool once) const {
    if (once) return (keys_[key] && !keys_last_[key]);
    return keys_[key];
  }
  bool Input::key_released(SDL_Scancode key, bool once) const {
    if (once) return (!keys_[key] && keys_last_[key]);
    return !keys_[key];
  }
  bool Input::key_mod(SDL_Keymod mod) const {
    return (keymod_ & mod);
  }
  int Input::mouse_x() const { return mouse_pt_.x; }
  int Input::mouse_y() const { return mouse_pt_.y; }
  SDL_Point Input::mouse_point() const { return mouse_pt_; }
  bool Input::mouse_pressed(int button, bool once) const {
    if (once) {
      return (mouses_btn_ & SDL_BUTTON(button))
          && !(mouses_btn_last_ & SDL_BUTTON(button));
    }
    return (mouses_btn_ & SDL_BUTTON(button));
  }
  bool Input::mouse_released(int button, bool once) const {
    if (once) {
      return !(mouses_btn_ & SDL_BUTTON(button))
          && (mouses_btn_last_ & SDL_BUTTON(button));
    }
    return !(mouses_btn_ & SDL_BUTTON(button));
  }
  int Input::joy_axis(int joy, int axis) const {
    return joy_axis_.at(joy).at(axis);
  }
  Uint8 Input::joy_hat(int joy, int hat) const {
    return joy_hats_.at(joy).at(hat);
  }
  bool Input::joy_pressed(int joy, int button, bool once) const {
    if (once) {
      return joy_axis_.at(joy).at(button)
          && !(joy_btns_last_.at(joy).at(button));
    }
    return joy_axis_.at(joy).at(button);
  }
  bool Input::joy_released(int joy, int button, bool once) const {
    if (once) {
      return !(joy_axis_.at(joy).at(button))
          && (joy_btns_last_.at(joy).at(button));
    }
    return !(joy_axis_.at(joy).at(button));
  }

  int Input::joy_count() const { return joysticks_.size(); }
  int Input::joy_axis_count() const { return joy_axis_.size(); }
  int Input::joy_hat_count() const { return joy_hats_.size(); }
  int Input::joy_button_count() const { return joy_btns_.size(); }

  ///////////////////
  Renderer::Renderer() : render_(0), font_(0), texture_(0), target_(0) {}
  Renderer::~Renderer() {}
  Result Renderer::init(SDL_Renderer *render, Font *font, Texture *texture) {
    if (!render || !font || !texture) return Result::E_InvalidParam;
    render_ = render;
    font_ = font;
    texture_ = texture;
    destroy_target(target_);
    target_ = 0;
    return Result::S_OK;
  }
  void Renderer::uninit() {
    if (target_) destroy_target(target_);
  }

  SDL_Texture* Renderer::target() const {
    return SDL_GetRenderTarget(render_);
  }
  Result Renderer::set_target(SDL_Texture *t) {
    return SDL_SetRenderTarget(render_, t) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  SDL_Texture* Renderer::create_target(int width, int height, Uint32 format) {
    return SDL_CreateTexture(render_
        , format
        , SDL_TextureAccess::SDL_TEXTUREACCESS_TARGET
        , width, height);
  }
  void Renderer::destroy_target(SDL_Texture *t) {
    if (t) SDL_DestroyTexture(t);
  }

  Result Renderer::size(int *w, int *h) const {
    return SDL_GetRendererOutputSize(render_, w, h) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  SDL_Rect Renderer::viewport() const {
    SDL_Rect rect;
    SDL_RenderGetViewport(render_, &rect);
    return std::move(rect);
  }
  Result Renderer::set_viewport(const SDL_Rect *rect) {
    return SDL_RenderSetViewport(render_, rect) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  SDL_Rect Renderer::clip_rect() const {
    SDL_Rect rect;
    SDL_RenderGetClipRect(render_, &rect);
    return std::move(rect);
  }
  Result Renderer::set_clip_rect(const SDL_FRect *rect) {
    if (rect) {
      SDL_Rect rc;
      rc.x = rect->x; rc.y = rect->y;
      rc.w = rect->w; rc.h = rect->h;
      return SDL_RenderSetClipRect(render_, &rc) == 0
          ? Result::S_OK : Result::E_Fail;
    } else {
      return SDL_RenderSetClipRect(render_, 0) == 0
          ? Result::S_OK : Result::E_Fail;
    }
  }
  Result Renderer::scale(float *x, float *y) const {
    SDL_RenderGetScale(render_, x, y);
    return Result::S_OK;
  }
  Result Renderer::set_scale(float x, float y) {
    return SDL_RenderSetScale(render_, x ,y) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  SDL_Color Renderer::draw_color() const {
    SDL_Color c;
    SDL_GetRenderDrawColor(render_, &c.r, &c.g, &c.b, &c.a);
    return std::move(c);
  }
  Result Renderer::set_draw_color(const SDL_Color &c) {
    return SDL_SetRenderDrawColor(render_
        , c.r, c.g, c.b, c.a) == 0
            ? Result::S_OK : Result::E_Fail;
  }
  SDL_BlendMode Renderer::blend_mode() const {
    SDL_BlendMode mode = SDL_BLENDMODE_INVALID;
    SDL_GetRenderDrawBlendMode(render_, &mode);
    return std::move(mode);
  }
  Result Renderer::set_blend_mode(SDL_BlendMode mode) {
    return SDL_SetRenderDrawBlendMode(render_, mode) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  void Renderer::clear_draw(const SDL_Color *c) {
    if (c) SDL_SetRenderDrawColor(render_, c->r, c->g, c->b, c->a);
    SDL_RenderClear(render_);
  }
  void Renderer::cleardraw() {
    int w,h, ow, oh;
    SDL_QueryTexture(target_, 0, 0, &w, &h);
    SDL_GetRendererOutputSize(render_, &ow, &oh);
    if (ow != w || oh != h) {
      destroy_target(target_);
      target_ = create_target(ow, oh);
    }
    SDL_SetRenderTarget(render_, target_);
    SDL_SetRenderDrawColor(render_, 0, 0, 0, 255);
    SDL_SetRenderDrawBlendMode(render_, SDL_BlendMode::SDL_BLENDMODE_BLEND);
    SDL_RenderClear(render_);
  }
  void Renderer::present() {
    SDL_SetRenderTarget(render_, 0);
    SDL_RenderCopy(render_, target_, 0, 0);
    SDL_RenderPresent(render_);
  }

  Result Renderer::texture_size(const char *id, int *w, int *h) const {
    return texture_->size(id, w, h);
  }
  Result Renderer::texture_size(SDL_Texture *t, int *w, int *h) const {
    return texture_->size(t, w, h);
  }
  Result Renderer::text_size(const char *font, const char *text, int *w, int *h) {
    return font_->size(font, text, w, h);
  }
  Result Renderer::text_size(TTF_Font *font, const char *text, int *w, int *h) {
    return font_->size(font, text, w, h);
  }

  Result Renderer::draw_point(float x, float y) {
    return SDL_RenderDrawPointF(render_, x, y) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  Result Renderer::draw_points(const SDL_FPoint *points, int count) {
    return SDL_RenderDrawPointsF(render_, points, count) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  Result Renderer::draw_line(float x1, float y1, float x2, float y2) {
    return SDL_RenderDrawLineF(render_, x1, y1, x2, y2) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  Result Renderer::draw_lines(const SDL_FPoint *points, int count) {
    return SDL_RenderDrawLinesF(render_, points, count) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  Result Renderer::draw_rect(const SDL_FRect * rect) {
    return SDL_RenderDrawRectF(render_, rect) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  Result Renderer::draw_rects(const SDL_FRect *rects, int count) {
    return SDL_RenderDrawRectsF(render_, rects, count) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  Result Renderer::fill_rect(const SDL_FRect * rect) {
    return SDL_RenderFillRectF(render_, rect) == 0
        ? Result::S_OK : Result::E_Fail;
  }
  Result Renderer::fill_rects(const SDL_FRect *rects, int count) {
    return SDL_RenderFillRectsF(render_, rects, count) == 0
        ? Result::S_OK : Result::E_Fail;
  }

  Result Renderer::draw_texture(const char *id, int x, int y, const SDL_Rect *src) {
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_texture(id, &dst, src);
  }
  Result Renderer::draw_texture(const char *id, int x, int y, const SDL_Rect *src
      , const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) {
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_texture(id, &dst, src, angle, center, flip);
  }
  Result Renderer::draw_texture(SDL_Texture *t, int x, int y, const SDL_Rect *src) {
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_texture(t, &dst, src);
  }
  Result Renderer::draw_texture(SDL_Texture *t, int x, int y, const SDL_Rect *src
      , const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) {
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_texture(t, &dst, src, angle, center, flip);
  }
  Result Renderer::draw_texture(const char *id, const SDL_FRect *dst, const SDL_Rect *src) {
    auto t = texture_->get(id);
    return draw_texture(t, dst, src);
  }
  Result Renderer::draw_texture(const char *id, const SDL_FRect *dst, const SDL_Rect *src
      , const double angle, const SDL_FPoint *center
      , const SDL_RendererFlip flip) {
    auto t = texture_->get(id);
    return draw_texture(t, dst, src, angle, center, flip);
  }
  Result Renderer::draw_texture(SDL_Texture *t, const SDL_FRect *dst, const SDL_Rect *src) {
    if (!t) return Result::E_TextureNotFound;
    Result rst = Result::E_Fail;
    if (src && (src->w <= 0 || src->h <= 0)) {
      src = 0;
    }
    if (dst && (dst->w <= 0 || dst->h <= 0)) {
      SDL_FRect d = *dst;
      if (src) {
        d.w = src->w;
        d.h = src->h;
      } else {
        int w, h;
        texture_size(t, &w, &h);
        d.w = w;
        d.h = h;
      }
      rst = SDL_RenderCopyF(render_, t, src, &d) == 0 ? Result::S_OK : Result::E_Fail;
    } else {
      rst = SDL_RenderCopyF(render_, t, src, dst) == 0 ? Result::S_OK : Result::E_Fail;
    }
    return rst;
  }
  Result Renderer::draw_texture(SDL_Texture *t, const SDL_FRect *dst, const SDL_Rect *src
      , const double angle, const SDL_FPoint *center
      , const SDL_RendererFlip flip) {
    if (!t) return Result::E_TextureNotFound;
    Result rst = Result::E_Fail;
    if (src && (src->w <= 0 || src->h <= 0)) {
      src = 0;
    }
    if (dst && (dst->w <= 0 || dst->h <= 0)) {
      SDL_FRect d = *dst;
      if (src) {
        d.w = src->w;
        d.h = src->h;
      } else {
        int w, h;
        texture_size(t, &w, &h);
        d.w = w;
        d.h = h;
      }
      rst = SDL_RenderCopyExF(render_, t, src, &d, angle, center, flip) == 0 ? Result::S_OK : Result::E_Fail;
    } else {
      rst = SDL_RenderCopyExF(render_, t, src, dst, angle, center, flip) == 0 ? Result::S_OK : Result::E_Fail;
    }
    return rst;
  }
  Result Renderer::draw_texture(const TextureData *data, int x, int y) {
    if (!data) return Result::E_InvalidParam;
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_texture(data->id.c_str(), &dst, &data->src);
  }
  Result Renderer::draw_texture(const TextureData *data, int x, int y
      , const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) {
    if (!data) return Result::E_InvalidParam;
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_texture(data->id.c_str(), &dst, &data->src, angle, center, flip);
  }
  Result Renderer::draw_texture(const TextureData *data, const SDL_FRect *dst) {
    if (!data) return Result::E_InvalidParam;
    return draw_texture(data->id.c_str(), dst, &data->src);
  }
  Result Renderer::draw_texture(const TextureData *data, const SDL_FRect *dst
      , const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) {
    if (!data) return Result::E_InvalidParam;
    return draw_texture(data->id.c_str(), dst, &data->src, angle, center, flip);
  }

  Result Renderer::draw_text(const char *fontid, const char *text, const SDL_Color &fg, int x, int y) {
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_text(fontid, text, fg, &dst);
  }
  Result Renderer::draw_text(const char *fontid, const char *text, const SDL_Color &fg, int x, int y
      , const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) {
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_text(fontid, text, fg, &dst, angle, center, flip);
  }
  Result Renderer::draw_text(TTF_Font *font, const char *text, const SDL_Color &fg, int x, int y) {
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_text(font, text, fg, &dst);
  }
  Result Renderer::draw_text(TTF_Font *font, const char *text, const SDL_Color &fg, int x, int y
      , const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) {
    SDL_FRect dst;
    dst.x = x; dst.y = y; dst.w = 0; dst.h = 0;
    return draw_text(font, text, fg, &dst, angle, center, flip);
  }
  Result Renderer::draw_text(const char *fontid, const char *text, const SDL_Color &fg, const SDL_FRect *dst) {
    auto f = font_->get(fontid);
    return draw_text(f, text, fg, dst);
  }
  Result Renderer::draw_text(const char *fontid, const char *text, const SDL_Color &fg, const SDL_FRect *dst
      , const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) {
    auto f = font_->get(fontid);
    return draw_text(f, text, fg, dst, angle, center, flip);
  }
  Result Renderer::draw_text(TTF_Font *f, const char *text, const SDL_Color &fg, const SDL_FRect *dst) {
    if (!f) return Result::E_FontNotFound;
    auto s = TTF_RenderUTF8_Blended(f, text, fg);
    if (!s) return Result::E_Fail;
    auto t = SDL_CreateTextureFromSurface(render_, s);
    SDL_FreeSurface(s);
    if (!t) return Result::E_Fail;
    auto rst = draw_texture(t, dst);
    SDL_DestroyTexture(t);
    return rst;
  }
  Result Renderer::draw_text(TTF_Font *f, const char *text, const SDL_Color &fg, const SDL_FRect *dst
      , const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) {
    if (!f) return Result::E_FontNotFound;
    auto s = TTF_RenderUTF8_Blended(f, text, fg);
    if (!s) return Result::E_Fail;
    auto t = SDL_CreateTextureFromSurface(render_, s);
    SDL_FreeSurface(s);
    if (!t) return Result::E_Fail;
    auto rst = draw_texture(t, dst, 0, angle, center, flip);
    SDL_DestroyTexture(t);
    return rst;
  }

  ///////////////////
  Game::Game() : window_(0), render_(0)
      , fps_(0), real_fps_(0), delay_time_(0) {}
  Game::~Game() { uninit(); }
  Result Game::init(const char *title, int width, int height, Uint32 window_flags
      , Uint32 image_flags, Uint32 audio_flags) {
    if (window_) return Result::S_Initialized;

    if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
        return Result::E_Fail;

    window_ = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED
        , width, height, window_flags & (~SDL_WINDOW_SHOWN) | SDL_WINDOW_HIDDEN);
    if (!window_) return Result::E_Fail;

    Result rst = Result::S_OK;
    render_ = SDL_CreateRenderer(window_, -1, SDL_RENDERER_ACCELERATED);
    if (!render_) rst = Result::E_Fail;

    if (Succeeded(rst))
      rst = font_.init();
    if (Succeeded(rst))
      rst = texture_.init(render_, image_flags);
    if (Succeeded(rst))
      rst = audio_.init(audio_flags);
    if (Succeeded(rst))
      rst = input_.init();
    if (Succeeded(rst))
      rst = renderer_.init(render_, &font_, &texture_);

    if (Succeeded(rst))
      rst = load_resource(font_, texture_, audio_);
    
    if (Succeeded(rst)) {
      if (window_flags & SDL_WINDOW_SHOWN)
        SDL_ShowWindow(window_);
    } else {
      SDL_DestroyWindow(window_);
      window_ = 0;
    }

    return rst;
  }
  void Game::uninit() {
    unload_resource(font_, texture_, audio_);
    renderer_.uninit();
    input_.uninit();
    audio_.uninit();
    texture_.uninit();
    font_.uninit();
    if (render_) {
      SDL_DestroyRenderer(render_);
      render_ = 0;
    }
    if (window_) {
      SDL_DestroyWindow(window_);
      window_ = 0;
    }
    SDL_Quit();
  }
  void Game::run() {
    bool quit = false;
    SDL_Event evt;
    Uint32 frame_start, frame_time;
    Uint32 frameCount = 0, elapsed_time = 0;

    while (!quit) {
      frame_start = SDL_GetTicks();

      while (SDL_PollEvent(&evt)) {
        if (handle(evt)) continue;
        if (evt.type == SDL_QUIT) {
          quit = true;
        }
      }
      
      input_.update();
      update(input_, audio_);

      renderer_.cleardraw();
      render(renderer_);
      renderer_.present();

      frame_time = SDL_GetTicks() - frame_start;
      if (frame_time < delay_time_)
        SDL_Delay(delay_time_-frame_time);
      
      ++frameCount;
      elapsed_time += SDL_GetTicks() - frame_start;
      if (elapsed_time >= 1000) {
        real_fps_ = 1000 * frameCount / elapsed_time;
        frameCount = 0;
        elapsed_time = 0;
      }
    }
  }
  void Game::set_fps(Uint32 _fps) {
    fps_ = _fps;
    delay_time_ = fps_ == 0 ? 0 : 1000 / fps_;
  }
  Uint32 Game::fps() const { return fps_; }
  Uint32 Game::real_fps() const { return real_fps_; }

  Font& Game::font() { return font_; }
  Texture& Game::texture() { return texture_; }
  Audio& Game::audio() { return audio_; }
  Input& Game::input() { return input_; }
  Renderer& Game::renderer() { return renderer_; }

  Result Game::set_window_size(int w, int h) {
    if (!window_) return Result::E_Fail;
    SDL_SetWindowSize(window_, w, h);
    return Result::S_OK;
  }
  Result Game::window_size(int *w, int *h) {
    if (!w || !h) return Result::E_InvalidParam;
    if (!window_) return Result::E_Fail;
    SDL_GetWindowSize(window_, w, h);
    return Result::S_OK;
  }
  Result Game::close_window() {
    SDL_Event evt;
    evt.type = SDL_QUIT;
    SDL_PushEvent(&evt);
    return Result::S_OK;
  }
  Result Game::enter_fullscreen(bool real) {
    if (!window_) return Result::E_Fail;
    if (SDL_SetWindowFullscreen(window_
        , real ? SDL_WINDOW_FULLSCREEN 
            : SDL_WINDOW_FULLSCREEN_DESKTOP) != 0) {
      return Result::E_Fail;
    }
    return Result::S_OK;
  }
  Result Game::exit_fullscreen() {
    if (!window_) return Result::E_Fail;
    if (SDL_SetWindowFullscreen(window_
        , 0) != 0) {
      return Result::E_Fail;
    }
    return Result::S_OK;
  }

  Result Game::load_resource(Font &f, Texture &t, Audio &a) { return Result::S_FunctionNotOverride; }
  void Game::unload_resource(Font &f, Texture &t, Audio &a) {}
  bool Game::handle(const SDL_Event &evt) { return false; }
  void Game::update(Input &inp, Audio &aud) {}
  void Game::render(Renderer &r) {}
} // end namespace g2d